/**
* Copyright (c) By zengqh.
*
* This program is just for fun or demo, in the hope that it  
* will be useful, you can redistribute it and/or modify freely.
*
* Time: 2013/03/16
* File: MeshLoader.cpp
* Blog: http://www.cnblogs.com/zengqh/
**/

#include "MeshFile.h"
#include "SkeletonManager.h"

namespace HY
{
bool MeshLoader::load(Mesh& mesh, File& file)
{
	return loadParams(mesh, file, true);
}

bool MeshLoader::loadParams(Mesh& mesh, File& file, bool static)
{
	bool result = false;

	MeshFile::Chunk chunk = readChunk(file);
	switch (chunk.type)
	{
	case MeshFile::CHUNK_MESHFILE:
		result = readMeshFile(mesh, file, chunk, static);
		break;
	default:
		HY_ASSERT(0);
		break;
	}

	return result;
}

bool MeshLoader::save(Mesh& mesh, File& file)
{
	/** not impl. */
	return false;
}

MeshLoader::MeshLoader()
{

}

MeshLoader::~MeshLoader()
{

}

bool MeshLoader::readMeshFile(Mesh& mesh, File& file, MeshFile::Chunk &chunk, bool is_static) const
{
	MeshFile::MeshFileHeader mesh_file_header;

	if (file.Read(&mesh_file_header, sizeof(mesh_file_header)) &&
		mesh_file_header.magic == MeshFile::MAGIC &&
		mesh_file_header.version == MeshFile::VERSION)
	{
		bool result = true;

		MeshFile::Chunk chunk = readChunk(file);
		while (result && chunk.type && chunk.size)
		{
			switch (chunk.type)
			{
			case MeshFile::CHUNK_MATERIALS:
				result = readMaterials(mesh, file);
				break;
			case MeshFile::CHUNK_MESH:
				result = readMesh(mesh, file, is_static);
				break;
			case MeshFile::CHUNK_SKELETON:
				file.Seek(file.GetPosition + chunk.size - sizeof(MeshFile::Chunk));
				break;
			case MeshFile::CHUNK_ANCHORPOINTS:
				file.Seek(file.GetPosition + chunk.size - sizeof(MeshFile::Chunk));
				break;
			case MeshFile::CHUNK_ANIMATIONS:
				file.Seek(file.GetPosition + chunk.size - sizeof(MeshFile::Chunk));
				break;
			case MeshFile::CHUNK_MORPHTARGETANIMATION:
				file.Seek(file.GetPosition + chunk.size - sizeof(MeshFile::Chunk));
				break;
			case MeshFile::CHUNK_MESHBOUNDINGBOX:
				result = readMeshBoundingBox(mesh, file);
				break;
			default:
				HY_ASSERT(0);
				file.Seek(file.GetPosition + chunk.size - sizeof(MeshFile::Chunk));
				break;
			}

			chunk = readChunk(file);
		}

		return result;
	}

	return false;
}

bool MeshLoader::readMaterials(Mesh& mesh, File& file) const
{
	MeshFile::Materials mat_num_info;
	
	if (!file.Read(&mat_num_info, sizeof(mat_num_info)))
	{
		return false;
	}
	
	char szMaterial[256] = {0};
	for (uint32 i = 0; i < mat_num_info.mat_num; ++i)
	{
		if (!file.Read(szMaterial, 256))
		{
			HY_ASSERT(0);
			return false;
		}

		MaterialBase* mat = new MaterialBase(szMaterial);
		mesh.addMaterial(mat);
	}

	return true;
}

bool MeshLoader::readMesh(Mesh& mesh, File& file, bool is_static) const
{
	MeshFile::Mesh mesh_info;

	if (!file.Read(&mesh_info, sizeof(mesh_info)))
	{
		HY_ASSERT(0);
		return false;
	}

	for (uint32 i = 0; i < mesh_info.lod_levels; ++i)
	{
		MeshFile::Chunk chunk = readChunk(file);

		if (chunk.type != MeshFile::CHUNK_LODLEVEL)
		{
			HY_ASSERT(0);
			return false;
		}

		MeshLODLevel* lod_level = mesh.addLODLevel();
		if (!readLODLevel())
		{
			HY_ASSERT(0);
			return false;
		}

		for (uint32 i = 0; i < mesh_info.morph_targets; ++i)
		{
			MeshFile::Chunk morph_chunk = readChunk(file);
			if (chunk.type != MeshFile::CHUNK_MORPHTARGET)
			{
				HY_ASSERT(0);
				return false;
			}

			if (!readMorphTarget(file, is_static))
			{
				HY_ASSERT(0);
				return false;
			}
		}

	}

	return true;
}

bool MeshLoader::readLODLevel(File& file, MeshLODLevel& lod_level, bool is_static) const
{
	MeshFile::LODLevel lod_level_info;
	if (!file.Read(&lod_level_info, sizeof(lod_level_info)))
	{
		HY_ASSERT(0);
		return false;
	}

	lod_level.setLODDistance(lod_level_info.distance);

	bool is_skip = false;
	MeshFile::Chunk chunk = readChunk(file);

	if (chunk.type == MeshFile::CHUNK_INDEXBUFFER)
	{
		lod_level.createIndexBuffer();
		IndexBuffer* ib = lod_level.getIndexBuffer();
		if (!readIndexBuffer(file, *ib, is_static))
		{
			HY_ASSERT(0);
			return false;
		}
	}
	else
	{
		is_skip = true;
	}

	/** read geometries. */
	if (lod_level_info.nGeo > 0)
	{
		lod_level.createGeometries();
	}

	std::vector<Geometry>* geo_list = lod_level.getGeometries();
	geo_list->resize(lod_level_info.nGeo);

	for (uint32 i = 0; i < lod_level_info.nGeo; ++i)
	{	
		if (!is_skip || i > 0)
		{
			chunk = readChunk(file);
		}

		if (chunk.type != MeshFile::CHUNK_GEOMETRY)
		{
			HY_ASSERT(0);
			return false;
		}

		Geometry& geo = (*geo_list)[i];

		if (!readGeometry(file, geo))
		{
			HY_ASSERT(0);
			return false;
		}
	}

	return true;
}

bool MeshLoader::readIndexBuffer(File& file, IndexBuffer& idx_buf, bool is_static) const
{
	MeshFile::IndexBuffer idx_buf_info;
	if (!file.Read(&idx_buf_info, sizeof(idx_buf_info)))
	{
		HY_ASSERT(0);
		return false;
	}

	idx_buf.setElementType(idx_buf_info.elem_type);
	idx_buf.create(idx_buf_info.elems, is_static ? 
		BufferUsage::Static : BufferUsage::Dynamic);

	void* data = idx_buf.lock(Lock::WriteOnly);

	HY_ASSERT(data);

	file.Read(data, idx_buf_info.size);

	idx_buf.unlock();

	return true;
}

bool MeshLoader::readGeometry(File& file, Geometry& geo) const
{
	MeshFile::Geometry geo_info;
	if (!file.Read(&geo_info, sizeof(geo_info)))
	{
		HY_ASSERT(0);
		return false;
	}

	geo.setName(geo_info.name);
	geo.setFlags(geo_info.flags);
	geo.setActive(geo_info.active);
	geo.setPrimitiveType(geo_info.prim_type);
	geo.setMaterial(geo_info.nMat);
	geo.setStartIdx(geo_info.start_idx);
	geo.setIdxSize(geo_info.idx_size);
}

bool MeshLoader::readMorphTarget(File& file, Mesh& mesh, bool is_static) const
{
	MeshFile::MorphTarget morph_target_info;

	if (!file.Read(&morph_target_info, sizeof(morph_target_info)))
	{
		HY_ASSERT(0);
		return false;
	}


	/** read vertex buffer. */
	for (uint32 i = 0; i < morph_target_info.vertex_buffer; ++i)
	{
		MeshFile::Chunk chunk = readChunk(file);
		if (chunk.type != MeshFile::CHUNK_VERTEXBUFFER)
		{
			HY_ASSERT(0);
			return false;
		}

		if (!readVertexBuffer(file, *mesh.getVertexBuffer(), i, is_static))
		{
			HY_ASSERT(0);
			return 0;
		}
	}

	return true;
}

bool MeshLoader::readVertexBuffer(File& file, VertexBuffer& vb, uint32 idx, bool is_static) const
{
	MeshFile::VertexBuffer vertexbuffer_info;
	if (file.Read(&vertexbuffer_info, sizeof(vertexbuffer_info)))
	{
		HY_ASSERT(0);
		return false;
	}

	for (uint32 i = 0; i < vertexbuffer_info.vertex_attr; ++i)
	{
		MeshFile::Chunk chunk = readChunk(file);
		if (chunk.type != MeshFile::CHUNK_VERTEXATTRIBUTE)
		{
			HY_ASSERT(0);
			return false;
		}

		if (!readVertexAttribute(file, vb))
		{
			HY_ASSERT(0);
			return false;
		}
	}

	HY_ASSERT(vb.create(vertexbuffer_info.vertices, is_static ? BufferUsage::Static : BufferUsage::Dynamic));

	void* data = vb.lock(Lock::WriteOnly);
	HY_ASSERT(data);

	if (!file.Read(data, vertexbuffer_info.size))
	{
		HY_ASSERT(0);
		return false;
	}

	vb.unlock();

	return true;
}

bool MeshLoader::readVertexAttribute(File& file, VertexBuffer& vb) const
{
	MeshFile::VertexAttribute vertex_attr;

	if (!file.Read(&vertex_attr, sizeof(vertex_attr)))
	{
		HY_ASSERT(0);
		return false;
	}

	vb.addVertexAttribute(vertex_attr.semantic, vertex_attr.channel,
		vertex_attr.type);

	return true;
}

bool MeshLoader::readMeshBoundingBox(Mesh& mesh, File& file) const
{
	MeshFile::BoundingBox aabb;

	if (!file.Read(&aabb, sizeof(aabb)))
	{
		HY_ASSERT(0);
		return false;
	}

	mesh.setBoundingBox(aabb.vmin, aabb.vmax);
}

MeshFile::Chunk MeshLoader::readChunk(File& file) const
{
	MeshFile::Chunk chunk;
	if (!file.Read(&chunk, sizeof(chunk)))
	{
		chunk.size = 0;
		chunk.type = 0;
	}

	return chunk;
}

}